package com.rodvl.common.orm;

import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPQLQuery;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.Querydsl;
import org.springframework.data.jpa.repository.support.QuerydslJpaPredicateExecutor;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.NonNull;

import javax.persistence.EntityManager;
import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

/**
 * @author lzb22
 */
@Transactional
public class MyQueryDslJpaRepository<T> extends
		QuerydslJpaPredicateExecutor<T> implements MyQueryDslPredicateExecutor<T> {

	private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;

	private final EntityPath<T> path;
	private final PathBuilder<T> builder;
	private final Querydsl querydsl;

	public MyQueryDslJpaRepository(JpaEntityInformation<T, ?> entityInformation,
			EntityManager entityManager) {
		super(entityInformation, entityManager, DEFAULT_ENTITY_PATH_RESOLVER, null);
		this.path = DEFAULT_ENTITY_PATH_RESOLVER.createPath(entityInformation.getJavaType());
		this.builder = new PathBuilder<T>(path.getType(), path.getMetadata());
		this.querydsl = new Querydsl(entityManager, builder);
	}

	@Override
	@NonNull
	public Optional<T> findOne(@NonNull Predicate predicate) {
		return super.findOne(predicate);
	}

	@Override
	@NonNull
	public List<T> findAll(OrderSpecifier<?>... orders) {
		return super.findAll(orders);
	}

	@Override
	@NonNull
	public List<T> findAll(@NonNull Predicate predicate,@NonNull Sort sort) {
		return executeSorted(createQuery(predicate).select(path), sort);
	}

	@Override
	@NonNull
	public Page<T> findAll(@NonNull Predicate predicate,@NonNull Pageable pageable) {
		return super.findAll(predicate, pageable);
	}

	@Override
	@NonNull
	public List<T> findAll(@NonNull Predicate predicate) {
		return super.findAll(predicate);
	}

	public List<T> findAll(Sort sort) {
		return executeSorted(createQuery().select(path), sort);
	}

	@Override
	public Page<T> findAll(Pageable pageable) {
		final JPQLQuery<?> countQuery = createCountQuery();
		JPQLQuery<T> query = querydsl.applyPagination(pageable, createQuery().select(path));

		return PageableExecutionUtils.getPage(
				query.distinct().fetch(),
				pageable,
				countQuery::fetchCount);
	}

	private List<T> executeSorted(JPQLQuery<T> query, Sort sort) {
		return querydsl.applySorting(sort, query).distinct().fetch();
	}

	@Override
	@NonNull
	public List<T> findAll(Predicate predicate, Limitable limitable) {
		JPQLQuery<T> query = createQuery(predicate).select(path);
		if (limitable != null) {
			querydsl.applySorting(limitable.getSort(), query);
			Integer offset = limitable.getFirstResult();
			if (offset != null && offset > 0) {
				query.offset(offset);
			}
			Integer limit = limitable.getMaxResults();
			if (limit != null && limit > 0) {
				query.limit(limit);
			}
		}
		return query.fetch();
	}

}
